#include "Face.h"
#include "Object.h"
#include <cmath>

Face::Face(Object* owner)
{
    object = owner;
    normal.x=0.0;
    normal.y=0.0;
    normal.z=0.0;
    baricenter = Point(0,0,0);
}

Face::Face(Object* owner, int i1, int i2, int i3, int i4)
{
    object = owner;
    vertexIndices.push_back(i1);
    vertexIndices.push_back(i2);
    vertexIndices.push_back(i3);

    if (i4!=-1) vertexIndices.push_back(i4);

    normal.x=0.0;
    normal.y=0.0;
    normal.z=0.0;

    updateCenter();
}

void Face::updateNormal()
{
    vector<Vertex> &v = object->vertices;

    int j;
    normal.x=0.0;
    normal.y=0.0;
    normal.z=0.0;
    int n = vertexIndices.size();

    for (int i=0; i<n; i++) {
        j = (i+1)%n;

        normal.x += ((v[vertexIndices[i]].coord.z+v[vertexIndices[j]].coord.z)*
                     (v[vertexIndices[i]].coord.y-v[vertexIndices[j]].coord.y));
        normal.y += ((v[vertexIndices[i]].coord.x+v[vertexIndices[j]].coord.x)*
                     (v[vertexIndices[i]].coord.z-v[vertexIndices[j]].coord.z));
        normal.z += ((v[vertexIndices[i]].coord.y+v[vertexIndices[j]].coord.y)*
                     (v[vertexIndices[i]].coord.x-v[vertexIndices[j]].coord.x));
    }

    for (int i = 0; i < n; i++) {
        v[vertexIndices[i]].normal.x += normal.x;
        v[vertexIndices[i]].normal.y += normal.y;
        v[vertexIndices[i]].normal.z += normal.z;
    }

    normal.normalize();
}

bool rayTriangleIntersection(const Ray& ray, const Point&p0, const Point& p1, const Point& p2, Point& intersect, float& t, float& up, float& vp, bool twoside)
{
    const double EPSILON = 0.0001;

    /* find vectors for two edges sharing vert0 */
    Vector edge1=p1-p0;
    Vector edge2=p2-p0;

    /* begin calculating determinant - also used to calculate U parameter */
    Vector pvec = ray.v ^ edge2;

    /* if determinant is near zero, ray lies in plane of triangle */
    double det = edge1*pvec;

    if (twoside) {
        if (det < EPSILON) return false;

        /* calculate distance from vert0 to ray origin */
        Vector tvec = ray.p - p0;

        /* calculate U parameter and test bounds */
        double up =tvec*pvec;

        if (up < 0.0 || up > det)  return false;

        /* prepare to test V parameter */
        Vector qvec= tvec ^ edge1;

        /* calculate V parameter and test bounds */
        vp = ray.v*qvec;

        if (vp < 0.0 || up + vp > det) return false;

        /* calculate t, scale parameters, ray intersects triangle */
        t = edge2 * qvec;
        double inv_det = (float)1.0 / det;
        t *= float(inv_det);
        up *= float(inv_det);
        vp *= float(inv_det);
    } else {
        if (det > -EPSILON && det < EPSILON) return false;

        double inv_det = (double)1.0 / det;

        /* calculate distance from vert0 to ray origin */
        Vector tvec= ray.p - p0;

        /* calculate U parameter and test bounds */
        up = (tvec*pvec) * float(inv_det);

        if (up < 0.0f || up > 1.0f) return false;

        /* prepare to test V parameter */
        Vector qvec= tvec ^ edge1;

        /* calculate V parameter and test bounds */
        vp = (ray.v*qvec) * float(inv_det);

        if (vp < 0.0 || up + vp > 1.0) return false;

        /* calculate t, ray intersects triangle */
        t = (edge2*qvec) * float(inv_det);
    }

    if (t<0) return false;  // CAG: consideramos una semirecta, no una recta

    // Otra forma de calcular la interseccion seria:  (1-u-v)*p0 + u*p1 + v*p2
    intersect = ray.p+float(t)*ray.v;

    return (true);
}

bool Face::hit(const Ray& r, float tmin, float tmax, SurfaceHitRecord& rec) const
{
    Point intersection;
    float t, up, vp;
    Point p0 = object->vertices[vertexIndices[0]].coord;
    Point p1 = object->vertices[vertexIndices[1]].coord;
    Point p2 = object->vertices[vertexIndices[2]].coord;
    bool intersects = rayTriangleIntersection(r, p0, p1, p2, intersection, t, up, vp, false);
    rec.superIntersect = intersects;
    intersects = (t > tmin && t < tmax) ? intersects : false;


    if (!intersects && vertexIndices.size() > 3) {
        Point p3 = object->vertices[vertexIndices[3]].coord;
        intersects = rayTriangleIntersection(r, p0, p2, p3, intersection, t, up, vp, false);
        rec.superIntersect = intersects;
        intersects = (t > tmin && t < tmax) ? intersects : false;
    }

    if (intersects) {
        rec.n = this->normal;
        rec.p = intersection;
        rec.surface = this;
        rec.t = t;      
    }

    return intersects;
}

void Face::updateCenter() {
    Point bc;
    Vertex a,b,c,d;

    a = object->vertices[vertexIndices[0]];
    b = object->vertices[vertexIndices[1]];
    c = object->vertices[vertexIndices[2]];

    if (vertexIndices.size() == 3) {
        bc.x = (a.coord.x + b.coord.x + c.coord.x)/3;
        bc.y = (a.coord.y + b.coord.y + c.coord.y)/3;
        bc.z = (a.coord.z + b.coord.z + c.coord.z)/3;
    }
    else {
        d = object->vertices[vertexIndices[3]];

        bc.x = (a.coord.x + b.coord.x + c.coord.x + d.coord.x)/4;
        bc.y = (a.coord.y + b.coord.y + c.coord.y + d.coord.y)/4;
        bc.z = (a.coord.z + b.coord.z + c.coord.z + d.coord.z)/4;
    }

    baricenter = bc;
}

// baricentro
Point Face::center()
{
    return baricenter;
}
